// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;

using System.Data.SqlClient;

using DA = PfRep.DataAccess;
using Data = PfRep.DataEntities;
using Common = PfRep.Common;
using System.Diagnostics;
using System.Threading;

namespace PfRep.Library
{
    /// <summary>
    /// A delegate called back by the indexer when finished indexing one item
    /// </summary>
    /// <param name="currentItemName"></param>
    /// <param name="currentItemUrl"></param>
    /// <param name="currentItemNumber"></param>
    /// <param name="itemCount"></param>
    public delegate void IndexingProgress(
        string currentItemName, 
        string currentItemUrl,
        int currentItemNumber,
        int itemCount);

    /// <summary>
    /// A delegate called back indicating when the indexing finished
    /// </summary>
    public delegate void IndexingCompleted();

    /// <summary>
    /// A delegate called back indicating an error
    /// </summary>
    /// <param name="e">The error itself</param>
    public delegate void IndexingError(Exception e);

    public class InsertIndexedFolderParams
    {
        public int ContentIndexId;
        public Data.Folder Folder;
        public SqlConnection Connection;

        public InsertIndexedFolderParams(
            int contentIndexId,
            Data.Folder folder,
            SqlConnection connection)
        {
            ContentIndexId = contentIndexId;
            Folder = folder;
            Connection = connection;
        }
    }

    /// <summary>
    /// The BL class responsible for indexing a folder tree
    /// </summary>
    public class IndexManager
    {
        private int _numberOfPublicFolders = 0;
        private int _currentItemNumber = 0;

        /// <summary>
        /// Fired whenever a progress changes
        /// </summary>
        public event IndexingProgress ProgressChanges;

        /// <summary>
        /// Fired when the indexing completed
        /// </summary>
        public event IndexingCompleted IndexingCompleted;

        /// <summary>
        /// Fired when an error occurred
        /// </summary>
        public event IndexingError IndexingError;

        /// <summary>
        /// Retrieves the number of folders from the consolidated database
        /// </summary>
        /// <param name="folderTreeId">Public folder tree ID</param>
        /// <returns>Number of folders</returns>
        public static int GetNumberOfPublicFolders(int folderTreeId)
        {
            using (SqlConnection sqlConnection = DA.SQL.CreateConnection())
            {
                return DA.SQL.GetNumberOfPublicFolders(folderTreeId, sqlConnection);
            }
        }

        /// <summary>
        /// Creates a new public folder traverse index
        /// </summary>
        /// <param name="publicFolderUrl">Url to the public folder to traverse</param>
        /// <param name="username">Username for the Exchange store</param>
        /// <param name="password">Password</param>
        /// <param name="indexName">Name to give to the index</param>
        public void CreatePublicFolderIndex(
            string publicFolderUrl, 
            string username,
            string password,
            string indexName)
        {
            // We wrap the whole process in a try-catch block and raise a
            //  special event when an error occurred. This is needed to handle
            //  the exception on the UI thread and not crash the process.
            try
            {
                // Creates a connection to the Exchange folder
                ADODB.Connection exchangeConnection =
                    DA.Exchange.CreateConnection(publicFolderUrl, username, password);
                Common.Diagnostics.Trace("CreatePublicFolderIndex", "Exchange connection opened");

                // Creates a connection to the SQL Server
                using (SqlConnection sqlConnection = DA.SQL.CreateConnection())
                {
                    Common.Diagnostics.Trace("CreatePublicFolderIndex", "SQL connection opened");

                    // Creates a new index record and retrieves its ID
                    int indexId = DA.SQL.CreateNewFolderIndex(publicFolderUrl, indexName, sqlConnection);
                    Common.Diagnostics.Trace("CreatePublicFolderIndex", "New tree index created");

                    // Creates a Pf index recursively
                    CreatePfIndexRecursive(indexId, exchangeConnection, sqlConnection,
                        publicFolderUrl, null);
                    Common.Diagnostics.Trace("CreatePublicFolderIndex", "Recursive processing finished");
                }
            }

            // We ignore this exception, because the user initiates it (Cancels the job)
            catch (ThreadAbortException)
            { }

            catch (Exception e)
            {
                // Trace the error
                Common.Diagnostics.Trace("CreatePublicFolderIndex", "Error occurred while indexing:" +
                    e.ToString());
                
                // Raise the event
                if (IndexingError != null)
                    IndexingError(e);
            }

            // Call the Finished event when the processing completed
            if (IndexingCompleted != null)
                IndexingCompleted();
        }

        /// <summary>
        /// Recirsively discovers a folder tree and inserts each node into a SQL Server
        /// </summary>
        /// <param name="indexId">Folder tree index ID that the folders belong to</param>
        /// <param name="exchangeConnection">Exchange connection object</param>
        /// <param name="sqlConnection">SQL connection object</param>
        /// <param name="parentUrl">An URL on the Exchange Server that has to be discovered</param>
        /// <param name="parentId">The very same folder's (as the URL) database record ID</param>
        private void CreatePfIndexRecursive(
            int indexId,
            ADODB.Connection exchangeConnection, 
            SqlConnection sqlConnection, 
            string parentUrl,
            int? parentId)
        {
            // Retrieve the subfolders
            List<Data.Folder> subFolders = DA.Exchange.GetSubfolders(exchangeConnection, parentUrl);

            // Handle each subfolders
            foreach (Data.Folder folder in subFolders)
            {
                // Insert a record for each folder
                int folderId = DA.SQL.InsertFolderToFolderIndex(indexId, folder, parentId, sqlConnection);

                // Call the function recursively
                CreatePfIndexRecursive(indexId, exchangeConnection, sqlConnection, folder.Key, folderId);

                // If there's any subscriptions, we fire them!
                //  Note: we don't know the count of public folders, because Public Folder
                //  stores don't support DEEP TRAVERSALs (recursive searches), so we would
                //  have to traverse the whole tree in order to count the number of folders,
                //  and this is what we are doing now to create the tree index.
                if (ProgressChanges != null)
                    ProgressChanges(folder.Name, folder.Key,
                        ++_currentItemNumber, -1);
            }
        }

        static void ThreadProc(object stateInfo)
        {
            // Get the input parameters
            InsertIndexedFolderParams inputParams =
                stateInfo as InsertIndexedFolderParams;

            // Call the SQL proc
            DA.SQL.InsertIndexedFolderData(
                inputParams.ContentIndexId, inputParams.Folder, inputParams.Connection);
        }

        /// <summary>
        /// Creates content statistics for a given public folder tree
        /// </summary>
        /// <param name="folderTreeId"></param>
        /// <param name="folderTreeTopUrl"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <param name="foldersToIndex"></param>
        public void CreateContentStatistics(
            string contentStatisticsName,
            int folderTreeId, 
            string folderTreeTopUrl,
            string username,
            string password,
            List<Data.Folder> foldersToIndex)
        {
            // Check input parameters
            Debug.Assert(!string.IsNullOrEmpty(contentStatisticsName));
            Debug.Assert(!string.IsNullOrEmpty(folderTreeTopUrl));
            Debug.Assert(!string.IsNullOrEmpty(username));
            Debug.Assert(!string.IsNullOrEmpty(password));
            Debug.Assert(foldersToIndex != null);
            Debug.Assert(folderTreeId > 0);

            // We wrap the whole process in a try-catch block and raise a
            //  special event when an error occurred. This is needed to handle
            //  the exception on the UI thread and not crash the process.
            try
            {

                // Retrieve the number of folders to index
                int numberOfFoldersToIndex = foldersToIndex.Count;

                // Creates a connection to the Exchange folder
                ADODB.Connection exchangeConnection =
                    DA.Exchange.CreateConnection(folderTreeTopUrl, username, password);
                Common.Diagnostics.Trace("CreateContentStatistics", "Exchange connection opened");

                // Open a Sql connection
                using (SqlConnection sqlConnection = DA.SQL.CreateConnection())
                {
                    Common.Diagnostics.Trace("CreateContentStatistics", "SQL connection opened");

                    // Create a new content index catalog
                    int newContentIndexCatalog =
                        DA.SQL.CreateNewContentIndex(
                        folderTreeId, contentStatisticsName, sqlConnection);

                    // Gets the current process to manage the maximum number
                    //  of threads running concurrently
                    //Process currentProcess = Process.GetCurrentProcess();

                    // Index each folder and inserts it into the database
                    int foldersAlreadyIndexed = 0;
                    foreach (Data.Folder folderToIndex in foldersToIndex)
                    {
                        // Retrieves index data from Exchange and stores it in the given folder instance
                        if (DA.Exchange.FillUpContentIndexProperties(folderToIndex, exchangeConnection))
                        {
                            // Wait until we are under 15 threads
                            /*do
                            {
                            } while (currentProcess.Threads.Count >= 5);*/

                            // Inserts the index data into SQL
                            ThreadProc(new InsertIndexedFolderParams(newContentIndexCatalog, folderToIndex, sqlConnection));
                            /*ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc),
                                new InsertIndexedFolderParams(newContentIndexCatalog, folderToIndex, sqlConnection));*/
                        }

                        // Communicates progress
                        if (ProgressChanges != null)
                            ProgressChanges(folderToIndex.Name, folderToIndex.Key,
                                ++foldersAlreadyIndexed, numberOfFoldersToIndex);
                    }
                }
            }

            // We ignore this exception, because the user initiates it (Cancels the job)
            catch (ThreadAbortException)
            { }

            catch (Exception e)
            {
                // Trace the error
                Common.Diagnostics.Trace("CreateContentStatistics",
                    "Error occurred while indexing contents:" +
                    e.ToString());

                // Raise the event
                if (IndexingError != null)
                    IndexingError(e);
            }

            // Call the Finished event when the processing completed
            if (IndexingCompleted != null)
                IndexingCompleted();
        }
    }
}
